<?php
/**
 * Tickets controller.
 * @package    sitrack
 * @author     Simon Downes <simon@simondownes.co.uk>
 * @copyright  Copyright (c) 2007, Simon Downes
 * @license    http://www.opensource.org/licenses/mit-license.php
 */

/**
 * Tickets controller.
 *
 * @package    sitrack
 * @author     Simon Downes <simon@simondownes.co.uk>
 * @copyright  Copyright (c) 2007, Simon Downes
 * @license    http://www.opensource.org/licenses/mit-license.php
 */
class Tickets_Controller extends SPF_Controller {

   /**
    * Constructor.
    * @return  void
    */
   public function __construct() {
      /* construct the parent object */
      parent::__construct(__CLASS__);
   }

   /**
    * Destructor.
    * @return  void
    */
   public function __destruct() {
   }

   /**
    * Determines if the client is permitted to access this controller and perform the requested action.
    * Access to this controller requires a signed-in user.
    *
    * @return  boolean   true if access is allowed, false otherwise.
    */
   public function authenticate() {

      return (SPF::$session->user != NULL);

   } // authenticate

   /**
    * Should only get here if there is no project stored in the session.
    * So we should prompt the user to select a project.
    *
    * @return  void
    */
   public function index() {
      
      $projects = Project::browse_data();
      
      // if only one project then go straight to it
      if( count($projects) == 1 )
         SPF::redirect('tickets', 'browse', $projects[0]['id']);
      
      $this->assign('projects', $projects);
      
      $this->display('index');
      
   } // index

   /**
    * Lists the tickets in the specified project.
    *
    * @param   integer   $project_id   the id of the project to look at.
    * @return  void
    */
   public function browse( $project_id = NULL ) {

      // if no project_id was specified then prompt user to select a project
      if( $project_id == NULL ) {
         SPF::$session->current_project = NULL;
         SPF::redirect('tickets');
      }

      $project = new Project();
      $project->fetch( (int) $project_id );

      // if project doesn't exist then tell user and prompt them to select one that does
      if( $project_id && !$project->id ) {
         SPF::$session->current_project = NULL;
         SPF::flash(SiTrack::MSG_NOT_FOUND, 'error');
         SPF::redirect('tickets');
      }

      // store selected project in the users session
      SPF::$session->current_project = $project->to_array();

      // if no filter in session then create a new one
      $filter = new SPF_Filter('ticket');
      if( !$filter->has_conditions() ) {

         // normal options
         $filter->add_condition('ticket_id', 'tickets.id', '=', 'NUMBER');
         $filter->add_condition('title', 'tickets.title', 'CONTAINS', 'STRING');
         $filter->add_condition('ticket_type', 'tickets.ticket_type_id', '=', 'NUMBER', NULL, 'ticket_type');
         $filter->add_condition('ticket_status', 'tickets.ticket_status_id', '=', 'NUMBER', NULL, 'ticket_status');
         $filter->add_condition('assigned_user', 'tickets.assigned_user', '=', 'STRING');

         // advanced options
         $filter->add_condition('component', 'tickets.component_id', '=', 'NUMBER', NULL, 'components');
         $filter->add_condition('milestone', 'tickets.milestone_id', '=', 'NUMBER', NULL, 'milestones');
         $filter->add_condition('ticket_priority', 'tickets.ticket_priority_id', '=', 'NUMBER', NULL, 'ticket_priority');
         $filter->add_condition('ticket_severity', 'tickets.ticket_severity_id', '=', 'NUMBER', NULL, 'ticket_severity');
         $filter->add_condition('created_user', 'tickets.created_user', '=', 'STRING');
         $filter->add_condition('include_closed', 'ticket_status.system_type', '!', 'STRING', 'OPEN');

         $filter->update();

      } // setup filter

      // store current filter
      $filter->save();

      // strip page and order by parameters from current url
      $query = SPF::$route['query'];
      unset( $query['page'] );
      unset( $query['orderby_by'] );

      $base_url = SPF::get_base_url( SPF::$route['controller'], SPF::$route['action'], SPF::$route['params'], $query );
      $base_url = str_replace('&', '&amp;', $base_url);

      $this->assign('base_url', str_replace('&', '&amp;', $base_url));

      $this->assign('project_id', $project_id);
      $this->assign('tickets', Ticket::browse_data($project_id, $filter));

      $filter_arr = $filter->to_array();
      if( $filter_arr['order_dir'] == 'DESC' ) {
         $filter_arr['order_dir'] = 'Desc';
         $filter_arr['toggle_order_dir'] = 'Asc';
      }
      else {
         $filter_arr['order_dir'] = 'Asc';
         $filter_arr['toggle_order_dir'] = 'Desc';
      }
      $filter_arr['conditions']['assigned_user']['options'] = User::lookup_options();
      $filter_arr['conditions']['created_user']['options'] = User::lookup_options();
      $this->assign('filter', $filter_arr);

      $this->display('browse');

   } // browse

   /**
    * Displays a summary of the specified ticket and allows the user to update the status and post a comment.
    *
    * @param   integer   $id   the id of the ticket to view.
    * @return  void
    */
   public function view( $ticket_id ) {

      // load the requested project object
      $ticket = new Ticket();
      $ticket->fetch( (int) $ticket_id );

      // if no such ticket then tell the user
      if( !$ticket->id ) {
         SPF::flash(SiTrack::MSG_NOT_FOUND, 'error');
         SPF::redirect('tickets', 'browse', SPF::$session->current_project['id']);
      }

      // store selected project in the users session
      $project = new Project();
      $project->fetch( $ticket->project_id );
      SPF::$session->current_project = $project->to_array();

      // if form was submitted then update the ticket accordingly
      if( SPF::form_submitted('frm_update_ticket') ) {

         $ticket->ticket_status_id = SPF::post('ticket_status_id');
         $ticket->assigned_user    = SPF::post('assigned_user');
         $ticket->validate();

         $comment = new Comment();
         //$comment->add_validation('details', false, 'alphanumeric', "\r\n". SPF::$config['allowed_chars'] );
         $comment->details = SPF::post('comment_details');

         if( $comment->details ) {
            $comment->ticket_id    = $ticket->id;
            $comment->created_user = SPF::$session->user['user_name'];
            $comment->created_date = SPF_Date::mysql_date(NULL, true);
            $comment->validate();
         }

         // if no errors then update
         if ( !$ticket->has_errors() && !$comment->has_errors() ) {
            // update comments first so the first history record created will be that a user made a comment,
            // followed by any assignment and status changes
            if( $comment->details )
               $comment->update(false);
            $ticket->update(false);
            SPF::flash(SiTrack::MSG_SAVED, 'success');
         }
         else {
            $this->assign('comment', $comment->to_array());
         }

      } // form submitted

      $this->assign('ticket', $ticket->to_array(true));
      $this->assign('comments', Ticket::get_comments($ticket->id));
      $this->assign('history', Ticket::get_history($ticket->id));
      $this->assign('attachments', Ticket::get_attachments($ticket->id));

      $this->assign('ticket_status', SPF::$db->lookup_options('ticket_status', $ticket->ticket_status_id));
      $this->assign('users',  User::lookup_options());

      $this->display('view');

   } // view

   /**
    * Allows the user to edit a ticket.
    *
    * @param   integer   $ticket_id   the id of the ticket to edit, if not specified a new ticket will be created.
    * @return  void
    */
   public function edit( $ticket_id = NULL ) {

      // load the requested project object
      $ticket = new Ticket();
      $ticket->fetch( (int) $ticket_id );

      // if no project id then use the current project stored in the session
      if( !$ticket->project_id ) {
         if( !SPF::$session->current_project )
            SPF::redirect('tickets');
         else
            $ticket->project_id = (int) SPF::$session->current_project['id'];
      }
      // otherwise update the session project to match the ticket
      else {
         $project = new Project();
         $project->fetch( $ticket->project_id );
         SPF::$session->current_project = $project->to_array();
      }

      if ( SPF::form_submitted('frm_ticket') ) {

         // run html_entity_decode() against each element in the post array to make sure we're not
         // inserting html into the database. this is only an issue when re-saving existing records
         // as when output to the browser the data will have html entities encoded and we don't want
         // to be saving the encoded entities into the database...
         $data = array_map('html_entity_decode', SPF::post());
         $ticket->from_array($data);

         if( !$ticket->created_user || !$ticket->created_date || $ticket->created_date === SPF_Database_MySQL::NULL_DATE ) {
            $ticket->created_user = SPF::$session->user['user_name'];
            $ticket->created_date = SPF_Date::mysql_date(NULL, true);
         }

         $ticket->validate();

         // if no errors then assign values to dataobject and update
         if ( !$ticket->has_errors() ) {
            $ticket->update(false);
            SPF::flash(SiTrack::MSG_SAVED, 'success');
            SPF::redirect('tickets', 'view', $ticket->id);
         }

      } // is_submitted

      $this->assign('ticket', $ticket->to_array());

      $this->assign('projects', SPF::$db->lookup_options('projects', $ticket->project_id));
      $this->assign('components', Project::lookup_options($ticket->project_id, 'components', $ticket->component_id));
      $this->assign('milestones', Project::lookup_options($ticket->project_id, 'milestones', $ticket->milestone_id));
      $this->assign('ticket_type', SPF::$db->lookup_options('ticket_type', $ticket->ticket_type_id));
      $this->assign('ticket_status', SPF::$db->lookup_options('ticket_status', $ticket->ticket_status_id));
      $this->assign('ticket_severity', SPF::$db->lookup_options('ticket_severity', $ticket->ticket_severity_id));
      $this->assign('ticket_priority', SPF::$db->lookup_options('ticket_priority', $ticket->ticket_priority_id));
      $this->assign('users', User::lookup_options($ticket->assigned_user));

      $this->display('edit');

   } // edit

   /**
    * Allows the user to delete a ticket.
    * All associated comments will also be deleted.
    *
    * @param   integer   $ticket_id   the id of the ticket to delete.
    * @return  void
    */
   public function delete( $ticket_id = NULL ) {

      // load the request object
      if( $ticket_id ) {
         $ticket = new Ticket();
         $ticket->delete( (int) $ticket_id );
         SPF::flash(SiTrack::MSG_DELETED, 'success');
      }

      SPF::redirect('tickets', 'browse', SPF::$session->current_project['id']);

   } // delete

   /**
    * Allows the user to delete a specific comment.
    * This should only be allowed if the current user posted the latest comment.
    *
    * @param   integer   $ticket_id    the id of the ticket to which the comment belongs.
    * @param   integer   $comment_id   the id of the comment to delete.
    * @return  void
    */
   public function delete_comment( $ticket_id = NULL, $comment_id = NULL ) {

      $ticket = new Ticket();
      $ticket->fetch( (int) $ticket_id );

      if( !$ticket->id ) {
         SPF::flash(SiTrack::MSG_NOT_FOUND, 'error');
         SPF::redirect('tickets', 'browse', SPF::$session->current_project['id']);
      }
      elseif( (int) $comment_id ) {
         $comment = new Comment();
         $comment->delete( (int) $comment_id );
         SPF::flash(SiTrack::MSG_DELETED, 'success');
      }

      SPF::redirect('tickets', 'view', $ticket->id);

   } // delete_comment

   /**
    * Uses the quick search box in the navigation bar to find tickets.
    * The filter applied depends on the criteria submitted:
    * - integers are assumed to be ticket ids and the user is redirected to view the appropriate ticket.
    * - strings that are user names will display tickets assigned to that user.
    * - everything else will be searched for in the ticket titles.
    *
    * @return  void
    */
   public function quick_search() {

      $value = trim(SPF::post('quick_search'));

      if( !$value )
         SPF::redirect('tickets', 'browse', SPF::$session->current_project['id']);

      // if value is an integer then assume it's a ticket_id
      if( SPF_Validator::is_integer($value) ) {
         SPF::redirect('tickets', 'view', (int) $value);
      }

      SPF_Filter::clear_filter('ticket');

      // otherwise check if string is a user name and if so then show tickets assigned to that user
      $user = new User();
      if( $user->exists( $value ) ) {
         SPF::redirect('tickets', 'browse', SPF::$session->current_project['id']. '&assigned_user='. $value);
      }

      // TODO: if value can be converted to a date then look for tickets created on that date.

      // otherwise look for the string in the ticket titles
      SPF::redirect('tickets', 'browse', SPF::$session->current_project['id']. '&title='. $value);

   } // quick_search

   /**
    * Exports tickets to a CSV file.
    * Tickets are filtered according to the filter specified in the browse() method.
    *
    * @return  void
    */
   public function export() {

      $project_id = SPF::$session->current_project['id'];

      // if no filter in session then create a new one
      $filter = new SPF_Filter('ticket');

      $tickets = Ticket::export_data($project_id, $filter);

      $headers = array('ID', 'Title', 'Project', 'Component', 'Milestone', 'Type', 'Status', 'Priority', 'Severity', 'Assigned To', 'Created By', 'Created Date');

      $tmp_file = SPF_APP_DIR. '/cache/'. SPF::$session->user['user_name']. '-export.csv';
      SPF::$db->export_data($tickets, $tmp_file, ',', $headers);

      // output CSV content
      header('content-type: text/csv');
      header('content-length: '. filesize($tmp_file));
	   header('content-disposition: attachment; filename=sitrack-export.csv');

      echo file_get_contents($tmp_file);

      unlink($tmp_file);

   } // export

   /**
    * Allows the user to upload a file and associate it with a ticket.
    *
    * @param   integer   $ticket_id   the id of the ticket to edit, if not specified a new ticket will be created.
    * @return  void
    */
   public function manage_attachments( $ticket_id = NULL ) {

      // load the requested project object
      $ticket = new Ticket();
      $ticket->fetch( (int) $ticket_id );

      // if no such ticket then tell the user
      if( !$ticket->id ) {
         SPF::flash(SiTrack::MSG_NOT_FOUND, 'error');
         SPF::redirect('tickets', 'browse', SPF::$session->current_project['id']);
      }

      // if form was submitted then upload the attachment
      if( SPF::form_submitted('frm_attachment') ) {

         if( isset($_FILES['src_file']) && Ticket::put_attachment( $ticket->id, $_FILES['src_file'] ) )
            SPF::flash(SiTrack::MSG_UPLOADED, 'success');
         else
            SPF::flash(SiTrack::MSG_UPLOAD_FAILED, 'error');

      } // form submitted

      $this->assign('ticket', $ticket->to_array(true));
      $this->assign('attachments', Ticket::get_attachments($ticket->id));
      $this->assign('max_size', ini_get('upload_max_filesize'));

      $this->display('manage_attachments');

   } // manage_attachments

   /**
    * Allows the user to download a specific ticket attachment.
    *
    * @param   integer   $ticket_id    the id of the ticket to which the comment belongs.
    * @param   integer   $file_id      the id of the file to download.
    * @return  void
    */
   public function download_attachment( $ticket_id = NULL, $file_id = NULL ) {

      $ticket_id = (int) $ticket_id;
      $file_id   = (int) $file_id;

      $file_info = Ticket::get_attachment($ticket_id, $file_id);

      if( !$file_info )
         throw new SPF_404_Exception();

      // don't send any debug info so we don't mess up the file content
      SPF::$no_debug = true;

		header("Content-type: {$file_info['mime_type']}");
	   header("Content-length: {$file_info['file_size']}");
		header("Content-Disposition: attachment; filename=\"{$file_info['file_name']}\"");

      echo $file_info['file_content'];

   } // download_attachment

   /**
    * Allows the user to download a specific ticket attachment.
    *
    * @param   integer   $ticket_id    the id of the ticket to which the comment belongs.
    * @param   integer   $file_id      the id of the file to download.
    * @return  void
    */
   public function delete_attachment( $ticket_id = NULL, $file_id = NULL ) {

      $ticket_id = (int) $ticket_id;
      $file_id   = (int) $file_id;

      if( Ticket::delete_attachment($ticket_id, $file_id) )
         SPF::flash(SiTrack::MSG_DELETED, 'success');
      else
         SPF::flash(SiTrack::MSG_NOT_FOUND, 'error');

      SPF::redirect('tickets', 'manage_attachments', $ticket_id);

   } // delete_attachment

} // Tickets_Controller

?>
